# -*- shell-script -*-

# 00minimal - Directory-based VPD creation functions.

# This file is part of the Linux lsvpd package.

# (C) Copyright IBM Corp. 2004

# Maintained by  Martin Schwenke <martins@au.ibm.com>

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
    
# $Id: 00vpd,v 1.1 2006/04/11 18:38:28 emunson Exp $

# This module is always loaded.
true || return 0

######################################################################

vpd_create_hook ()
{
    # Uses: ds, ax, mf, tm, sn, cd, rl, rm, yl

    local node="$1"

    local vpd_dir
    vpd_dir_set_hook "$node"

    _vpd_field_append "value" "$vpd_dir" "DS" "$ds"
    _vpd_field_append "value" "$vpd_dir" "MF" "$mf"
    _vpd_field_append "value" "$vpd_dir" "TM" "$tm"
    _vpd_field_append "value" "$vpd_dir" "SN" "$sn"
    _vpd_field_append "value" "$vpd_dir" "CD" "$cd"
    _vpd_field_append "value" "$vpd_dir" "RL" "$rl"
    _vpd_field_append "value" "$vpd_dir" "RM" "$rm"
    _vpd_field_append "value" "$vpd_dir" "YL" "$yl"
}

vpd_field_add_value ()
{
    local dir="$1"
    local field="$2"
    local value="$3"

    ensure_directory "$dir"
    local f="${dir}/${field}"
    vpd_field_remove_dir "$f"
    echo -n "$value"  >"$f"
}

vpd_field_remove_dir ()
{
    local d="$1"

    if [ -d "$d" ] ; then
	local f
	for f in "link" "last" ; do
	    local t="${d}/${f}"
	    [ -f "$t" ] && rm -f "$t"
	done
	debug_cmd rmdir "$d"
    fi
}

vpd_field_add_link ()
{
    local dir="$1"
    local field="$2"
    local link="$3"

    ensure_directory "$dir"

    # Remove any old value.
    local f="${dir}/${field}"
    vpd_field_remove_dir "$f"
    rm -f "$f"

    # Create directory and link.
    ensure_directory "$f"
    ln -s "$link" "${f}/link"

    # Populate cache.
    local vpd_value
    vpd_field_get "$dir" "$field"
}

_vpd_field_add ()
{
    local flag="$1"
    shift

    case "$flag" in
	(value) vpd_field_add_value "$@" ;;
	(link)  vpd_field_add_link  "$@" ;;
    esac
}

_vpd_field_append ()
{
    local flag="$1"
    local dir="$2"
    local field="$3"
    local value="$4"

    if [ -n "$value" ] ; then 
	_vpd_field_add "$flag" "$dir" "$field" "$value"
	echo -n "${field} " >>"${dir}/.lsvpd"
    fi
}

_vpd_field_insert ()
{
    local flag="$1"
    local dir="$2"
    local field="$3"
    local value="$4"

    local f="${dir}/.lsvpd"
    if [ ! -f "$f" ] ; then
	_vpd_field_append "$flag" "$dir" "$field" "$value"
    else
	local all
	read all <"$f"
	# Important trailing space lost above!
	all="${all} "

	case "$field" in
	    FC)
	        # FC goes first.
		all="FC ${all}"
		;;
	    AX)
	        # Insert after DS, if possible.
		all="${all/DS /DS AX }"
		case "$all" in
		    (*DS\ AX*) : ;;
		    (*) all="${all}AX " ;;
		esac
		;;
	    ?*)
	        # Insert before YL, if possible.
		all="${all/YL /${field} YL }"
		case "$all" in
		    (*${field}\ YL*) : ;;
		    (*) all="${all}${field} " ;;
		esac
		;;
	esac

	echo "${all}" >"$f"
	_vpd_field_add "$flag" "$dir" "$field" "$value"
    fi
}

_vpd_field_ensure ()
{
    local flag="$1"
    local dir="$2"
    local field="$3"
    local value="$4"

    if [ ! -e "${dir}/${field}" ] ; then
	    _vpd_field_insert "$flag" "$dir" "$field" "$value"
    fi
}

vpd_field_ensure ()
{
    _vpd_field_ensure "value" "$@"
}

vpd_field_ensure_link ()
{
    _vpd_field_ensure "link" "$@"
}

_vpd_field_override ()
{
    local flag="$1"
    local dir="$2"
    local field="$3"
    local value="$4"

    local f="${dir}/${field}"
    if [ -e "$f" ] ; then
	_vpd_field_add    "$flag" "$dir" "$field" "$value"
    else
	_vpd_field_insert "$flag" "$dir" "$field" "$value"
    fi
}

vpd_field_override ()
{
    _vpd_field_override "value" "$@"
}

vpd_field_override_link ()
{
    _vpd_field_override "link" "$@"
}

adapter_extra_vpd_hooks=""

# Each hook can only set one VPD key/value pair, so expected to set
# $key and $val.  Yes, this is premature optimisation.  The hook can
# also set $vpd_function to choose how the key/value is merged with
# the VPD - vpd_field_ensure is used by default.
adapter_extra_vpd_hook ()
{
    local bus_type="$1"
    local bus_addr="$2"
    local node="$3"

    local f subtype
    local f="${node}/lsvpd,subtype"
    [ -r "$f" ] && read subtype <"$f"    

    local vpd_subdirs
    vpd_subdirs_list_hook "$node"

    if [ -n "$vpd_subdirs" -a -n "$subtype" ] ; then
	local f
	for f in $adapter_extra_vpd_hooks ; do
	    local vpd_function key val
	    $f "$bus_type" "$bus_addr" "$subtype"
	    if [ -n "$key" -a -n "$val" ] ; then
		[ -n "$vpd_function" ] || vpd_function=vpd_field_ensure
		local vpd_dir
		for vpd_dir in $vpd_subdirs ; do
		    $vpd_function "$vpd_dir" "$key" "$val"
		done
	    fi
	done
    fi
}

vpd_dir_set_hook ()
{
    local node="$1"
    local subnode="$2" ; [ -n "$subnode" ] || subnode="0"

    if [ $subnode -eq 0 ] ; then
	vpd_dir="${node}/linux,vpd/000"
    else
	vpd_dir=$(printf "${node}/linux,vpd/%03d" $subnode)
    fi
}

vpd_subdirs_list_hook ()
{
    local node="$1"

    local d="${node}/linux,vpd"

    if [ ! -d "${d}/001" ] ; then
	if [ -d "${d}/000" ] ; then
	    vpd_subdirs="${d}/000"
	else
	    vpd_subdirs=""
	fi
    else
        # shopt -s nullglob
	vpd_subdirs=$(echo "${d}/"[0-9][0-9][0-9])
    fi
}

vpd_fields_list_hook ()
{
    local vpd_dir="$1"

    local f="${vpd_dir}/.lsvpd"
    [ -r "$f" ] && read vpd_fields <"$f"
}

# For non-unique field names, this interface can be used in 2 ways.
# The 3 digit sequence number for fields (after the first, numbered 0,
# but omitted) can be appended to the field name (as in ZZ.002), or
# the sequence number can be provided as an optional 3rd argument.
vpd_field_get ()
{
    # Sets: vpd_value
    vpd_value=""

    local vpd_dir="$1"
    local field="$2"
    local seq="$3"

    local f="${vpd_dir}/${field}"
    if [ -n "$seq" ] ; then
	if [ "$seq" -gt 0 ] ; then
	    f=$(printf "%s.%03d" "$f" "$seq")
	fi
    fi

    local last="${f}/last"
    if [ -d "$f" ] ; then
	local link="${f}/link"
	if on_live_system && [ -r "$link" ] ; then
	    read vpd_value <"$link"
	    can_cache_vpd && \
		echo -n "$vpd_value" >"$last"
	elif should_show_cached_vpd && [ -r "$last" ] ; then
	    read vpd_value <"$last"
	fi
    elif [ -L "$f" ] && ! on_live_system ; then
	: # Backward compatibility.
    elif [ -r "$f" ] ; then
	read vpd_value <"$f"
    fi
}

vpd_node_override_value ()
{
    local node="$1"
    local key="$2"
    local val="$3"

    [ -n "$val" ] || return 0

    local vpd_subdirs
    vpd_subdirs_list_hook "$node"

    local vpd_dir
    for vpd_dir in $vpd_subdirs ; do
	vpd_field_override "$vpd_dir" "$key" "$val"
    done

    return 0
}
